package net.entelijan.config.impl2;

import java.beans.PropertyDescriptor;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeanWrapper;
import org.springframework.beans.BeanWrapperImpl;

public abstract class PathUtil {

	private static Log log = LogFactory.getLog(PathUtil.class);

	public PathUtil() {
		super();
	}

	public static List<List<String>> splitToPossiblePathes(String propertyName) {
		try {
			ArrayList<List<String>> re = new ArrayList<List<String>>();
			if (propertyName != null) {
				if (propertyName.length() > 0) {
					String[] pathElems = splitCamelCase(propertyName);
					log.debug("[findPathes] after splitCamelCase '"
							+ propertyName + "' -> "
							+ Arrays.toString(pathElems));
					for (List<List<Integer>> combi : calculateIndexCombinations(
							pathElems.length, 0)) {
						re.add(createStringItems(pathElems, combi));
					}
				}
			}
			return re;
		} catch (Exception e) {
			throw new IllegalStateException("Could not find pathes for '"
					+ propertyName + "' because: " + e.getMessage(), e);
		}
	}

	private static String[] splitCamelCase(String propName) {
		String[] re = new String[0];
		if (propName != null) {
			ArrayList<String> reList = new ArrayList<String>();
			StringBuilder b = new StringBuilder();
			for (int i = 0; i < propName.length(); i++) {
				char c = propName.charAt(i);
				if (i == 0) {
					b.append(c);
				} else {
					if (Character.isUpperCase(c)) {
						reList.add(b.toString());
						b = new StringBuilder();
						b.append(Character.toLowerCase(c));
					} else {
						b.append(c);
					}
				}
			}
			reList.add(b.toString());
			re = reList.toArray(new String[reList.size()]);
		}
		return re;
	}

	public static List<List<List<Integer>>> calculateIndexCombinations(
			int length, int offset) {
		List<List<List<Integer>>> re = new ArrayList<List<List<Integer>>>();
		if (length > 0) {
			List<List<Integer>> headList = createHeadList(offset, length);
			for (List<Integer> head : headList) {
				List<List<List<Integer>>> tails = calculateIndexCombinations(
						length - head.size(), head.get(head.size() - 1) + 1);
				if (tails.isEmpty()) {
					List<List<Integer>> combination = new ArrayList<List<Integer>>();
					combination.add(head);
					re.add(combination);
				} else {
					for (List<List<Integer>> tail : tails) {
						List<List<Integer>> combination = new ArrayList<List<Integer>>();
						combination.add(head);
						combination.addAll(tail);
						re.add(combination);
					}
				}
			}
		}
		return re;
	}

	private static List<List<Integer>> createHeadList(int offset, int length) {
		List<List<Integer>> re = new ArrayList<List<Integer>>();
		for (int size = length; size > 0; size--) {
			List<Integer> head = new ArrayList<Integer>();
			for (int i = 0; i < size; i++) {
				head.add((i + offset));
			}
			re.add(head);
		}
		return re;
	}

	private static List<String> createStringItems(String[] pathElems,
			List<List<Integer>> indexCombination) {
		ArrayList<String> re = new ArrayList<String>();
		for (List<Integer> row : indexCombination) {
			int cnt = 0;
			StringBuilder sb = new StringBuilder();
			for (int index : row) {
				if (cnt == 0) {
					sb.append(pathElems[index]);
				} else {
					sb.append(firstToUpperCase(pathElems[index]));
				}
				cnt++;
			}
			re.add(sb.toString());
		}
		return re;
	}

	private static String firstToUpperCase(String string) {
		String re;
		if (string == null) {
			re = null;
		} else if (string.length() == 0) {
			re = "";
		} else if (string.length() == 1) {
			re = string.toUpperCase();
		} else {
			re = "" + Character.toUpperCase(string.charAt(0))
					+ string.substring(1);
		}
		return re;
	}

	public static List<String> findMatchingPath(
			List<List<String>> possiblePathes, Class<?> modelClass) {
		List<String> re = null;
		log.debug("[findMatchingPath] modelClass:" + modelClass);
		try {
			BeanWrapper wModel = new BeanWrapperImpl(modelClass);
			for (PropertyDescriptor desc : wModel.getPropertyDescriptors()) {
				String name = desc.getName();
				List<List<String>> candidates = new ArrayList<List<String>>();
				for (List<String> path : possiblePathes) {
					if (path.size() == 1 && path.iterator().next().equals(name)) {
						re = path;
						break;
					} else if (path.size() >= 1
							&& path.iterator().next().equals(name)) {
						candidates.add(path);
					}
				}
				if (re == null && !candidates.isEmpty()) {
					re = selectCandidate(candidates, name, wModel);
				}
			}
		} catch (Exception e) {
			log.debug("Could not find matching path for " + modelClass);
		}
		return re;
	}

	private static List<String> selectCandidate(List<List<String>> candidates,
			String name, BeanWrapper wModel) {
		List<String> re = null;
		List<List<String>> tailPathes = createTailPathes(candidates);
		Class<?> childClass = createChildClass(name, wModel);
		if (childClass != null) {
			List<String> matchingTailPath = findMatchingPath(tailPathes,
					childClass);
			if (matchingTailPath != null && !matchingTailPath.isEmpty()) {
				for (List<String> candidate : candidates) {
					if (isTail(matchingTailPath, candidate)) {
						re = candidate;
						break;
					}
				}
			}
		}
		return re;
	}

	private static boolean isTail(List<String> tailPath, List<String> path) {
		boolean re = false;
		if (path.size() > 1 && path.size() - tailPath.size() == 1) {
			re = true;
			for (int i = 0; i < tailPath.size(); i++) {
				if (!path.get(i + 1).equals(tailPath.get(i))) {
					re = false;
					break;
				}
			}
		}
		return re;
	}

	private static Class<?> createChildClass(String name, BeanWrapper wModel) {
		Class<?> re = null;
		if (wModel.isReadableProperty(name)) {
			PropertyDescriptor desc = wModel.getPropertyDescriptor(name);
			re = desc.getPropertyType();
		}
		return re;
	}

	private static List<List<String>> createTailPathes(
			List<List<String>> candidates) {
		List<List<String>> tailPathes = new ArrayList<List<String>>();
		for (List<String> path : candidates) {
			List<String> tailPath = new ArrayList<String>();
			int i = 0;
			for (String elem : path) {
				if (i > 0) {
					tailPath.add(elem);
				}
				i++;
			}
			if (!tailPath.isEmpty()) {
				tailPathes.add(tailPath);
			}
		}
		return tailPathes;
	}

	public static String pathToString(List<String> path) {
		StringBuilder sb = new StringBuilder();
		for (int i = 0; i < path.size(); i++) {
			if (i > 0) {
				sb.append(".");
			}
			sb.append(path.get(i));
		}
		return sb.toString();
	}

	public static boolean containsKeywordIgnoreCase(List<String> path,
			String key) {
		boolean re = false;
		for (String item : path) {
			if (item.toUpperCase().equals(key.toUpperCase())) {
				re = true;
				break;
			}
		}
		return re;
	}

	public static boolean containsAnyKeywordIgnoreCase(
			List<List<String>> paths, String key) {
		boolean re = false;
		for (List<String> path : paths) {
			if (containsKeywordIgnoreCase(path, key)) {
				re = true;
				break;
			}
		}
		return re;
	}

	public static List<List<String>> removeSuffixesIgnoreCase(
			List<List<String>> pathes, String... suffixes) {
		List<List<String>> re;
		if (suffixes.length == 0) {
			re = pathes;
		} else {
			re = new ArrayList<List<String>>();
			for (List<String> path : pathes) {
				re.add(removeSuffixIgnoreCase(path, suffixes));
			}
		}
		return re;
	}

	private static List<String> removeSuffixIgnoreCase(List<String> path,
			String[] suffixes) {
		ArrayList<String> re = new ArrayList<String>();
		for (int i = 0; i < path.size(); i++) {
			if (i == (path.size() - 1)) {
				if (!equalsAnyIgnoreCase(path.get(i), suffixes)) {
					re.add(path.get(i));
				}
			} else {
				re.add(path.get(i));
			}
		}
		return re;
	}

	private static boolean equalsAnyIgnoreCase(String name, String[] names) {
		boolean re = false;
		for (String nam : names) {
			if (name.equalsIgnoreCase(nam)) {
				re = true;
				break;
			}
		}
		return re;
	}

	/**
	 * Returns the head of pathes that are cut by the occurrence of a boundary
	 * item.
	 * 
	 * @param pathes
	 *            Pathes that will be cut
	 * @param boundaryItem
	 *            The boundary item to define the cut point
	 * @return The cut pathes.
	 */
	public static List<List<String>> getHeads(List<List<String>> pathes,
			String boundaryItem) {
		List<List<String>> re = new ArrayList<List<String>>();
		for (List<String> path : pathes) {
			List<String> head = new ArrayList<String>();
			for (String item : path) {
				if (item.equals(boundaryItem)) {
					break;
				} else {
					head.add(item);
				}
			}
			re.add(head);
		}
		return re;
	}

	/**
	 * Compares two list of pathes if they match on the head
	 * 
	 * @param pathes
	 *            The pathes to be compared
	 * @param heads
	 *            The heads to be compared with
	 * @return True if any of the pathes heads matches any of the head pathes
	 */
	public static boolean hasMatchingHead(List<List<String>> pathes,
			List<List<String>> heads) {
		boolean re = false;
		for (List<String> path : pathes) {
			re = matchesAnyHead(path, heads);
		}
		return re;
	}

	private static boolean matchesAnyHead(List<String> path,
			List<List<String>> heads) {
		boolean re = false;
		for (List<String> headPath : heads) {
			if (matchesHead(path, headPath)) {
				re = true;
				break;
			}
		}
		return re;
	}

	private static boolean matchesHead(List<String> path, List<String> headPath) {
		if (path.size() == 0) {
			throw new IllegalArgumentException(
					"Size of path must be at least one");
		}
		boolean re = true;
		if (path.size() < headPath.size()) {
			re = false;
		} else {
			for (int i = 0; i < headPath.size(); i++) {
				if (!path.get(i).equals(headPath.get(i))) {
					re = false;
					break;
				}
			}
		}
		return re;
	}

	public static <T> Class<?> getPropertyType(T model,
			List<String> matchingPath) {
		BeanWrapper wModel = new BeanWrapperImpl(model);
		final PropertyDescriptor modelPropDesc = wModel
				.getPropertyDescriptor(PathUtil.pathToString(matchingPath));
		Class<?> propertyType = modelPropDesc.getPropertyType();
		return propertyType;
	}

	public static List<List<String>> removeHeads(List<List<String>> pathes) {
		ArrayList<List<String>> re = new ArrayList<List<String>>();
		for (List<String> path : pathes) {
			if (!path.isEmpty() && path.size() > 1) {
				re.add(removeHead(path));
			}
		}
		return re;
	}

	private static List<String> removeHead(List<String> path) {
		ArrayList<String> re = new ArrayList<String>();
		if (!path.isEmpty() && path.size() > 1) {
			for (int i = 1; i < path.size(); i++) {
				re.add(path.get(i));
			}
		}
		return re;
	}

}
